﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using CsvHelper;
using CsvHelper.Configuration.Attributes;
using DevExpress.Xpf.Core;
using DevExpress.Xpf.Grid;
using EnumsNET;
using Microsoft.Win32;
using MoreLinq;
using static System.Math;

namespace WindLoad
{
    public partial class MainWindow : ThemedWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            //
            InitControls();

            this.DataContext = this;
        }

        private void InitControls()
        {
            ComboBoxGroundType.ItemsSource = Enums.GetValues<GroundType>();
            ComboBoxGroundType.SelectedItem = GroundType.B;

            ComboBoxRiskArea.ItemsSource = Enums.GetValues<RiskArea>();
            ComboBoxRiskArea.SelectedItem = RiskArea.R2;

            GridTowerSizeData.ItemsSource = TowerSizeItems;
        }

        private string _mctFilePath;
        private readonly MctModel _mctModel = new MctModel();
        private ObservableCollection<TowerSizeItem> TowerSizeItems { get; } = new ObservableCollection<TowerSizeItem>();
        private ForceUnit _forceUnit = ForceUnit.KN; //输出节点力单位: kN
        private bool _hasVehicle = false;

        private void ButtonMctFilePath_OnClick(object sender, RoutedEventArgs e)
        {
            var ofd = new OpenFileDialog {DefaultExt = ".mct", Filter = "Midas Civil MCT file (.mct)|*.mct"};
            if (ofd.ShowDialog(this) != true)
                return;

            _mctFilePath = ofd.FileName;
            Debug.Assert(File.Exists(_mctFilePath));
            ButtonEditMctFile.Text = _mctFilePath;

            //
            _mctModel.ReadMctFileData(_mctFilePath);

            UpdateControls();
        }

        private void UpdateControls()
        {
            Debug.Assert(_mctModel.GroupDict != null);
            var loadCaseNames = _mctModel.StaticLoadCaseDict.Select(lc => lc.Key).ToList();
            var groupNames = _mctModel.GroupDict.Select(g => g.Key).ToList();

            //
            // 主梁
            //
            var names = loadCaseNames.Where(name => name.Contains("主梁")).ToList();
            ComboBoxGirderLoadCase.ItemsSource = names;
            ComboBoxGirderLoadCase.SelectedIndex = 0;

            names = groupNames.Where(name => name.Contains("主梁")).ToList();
            ComboBoxGirderElemGroup.ItemsSource = names;
            ComboBoxGirderElemGroup.SelectedIndex = 0;

            //
            // 主塔
            //
            names = loadCaseNames.Where(name => name.Contains("塔")).ToList();
            ComboBoxTowerLoadCase.ItemsSource = names;
            ComboBoxTowerLoadCase.SelectedIndex = 0;

            names = groupNames.Where(name => name.Contains("塔")).ToList();
            ComboBoxTowerElemGroup.ItemsSource = names;
            ComboBoxTowerElemGroup.SelectedIndex = 0;

            //
            // 斜拉索
            //
            names = loadCaseNames.Where(name => name.Contains("拉索")).ToList();
            ComboBoxCableLoadCase.ItemsSource = names;
            ComboBoxCableLoadCase.SelectedIndex = 0;

            names = groupNames.Where(name => name.Contains("拉索")).ToList();
            ComboBoxCableElemGroup.ItemsSource = names;
            ComboBoxCableElemGroup.SelectedIndex = 0;
        }

        #region 主梁

        private void ButtonGenerateGirderLoadMct_OnClick(object sender, RoutedEventArgs e)
        {
            var elemDict = _mctModel.ElemDict;
            var groupDict = _mctModel.GroupDict;
            var loadCaseDict = _mctModel.StaticLoadCaseDict;

            //
            // 设计参数
            //
            var loadCaseName = (string) ComboBoxGirderLoadCase.SelectedItem;
            Debug.Assert(loadCaseName.Contains("横风") || loadCaseName.Contains("纵风"));
            var isTransverseLoad = loadCaseName.Contains("横风");
            _hasVehicle = loadCaseName.Contains("有车");
            var sign = loadCaseName.EndsWith("-") ? -1D : 1D;

            //
            var groundType = (GroundType) ComboBoxGroundType.SelectedItem;
            var riskArea = (RiskArea) ComboBoxRiskArea.SelectedItem;
            var groundZ = (double) SpinEditGroundZ.Value; //水面标高
            var deckZ = (double) SpinEditDeckZ.Value; //桥面标高
            Debug.Assert(deckZ > groundZ);

            var z = deckZ - groundZ; // 主梁基准高度Z
            var us10 = _hasVehicle
                ? WindSpeed.GetUs10FromUd((double) SpinEditUdw1.Value, z, groundType, riskArea) //桥上有车时，以桥面风速(最大25m/s)反算
                : (double) SpinEditUs10.Value; //桥上无车时，设计基本风速

            var groupName = (string) ComboBoxGirderElemGroup.SelectedItem;
            var elemIds = groupDict[groupName].ElemIds; //"主梁桁架"单元组包含3片桁架
            var nodeIds = elemIds.SelectMany(id => elemDict[id].NodeIds)
                .Distinct().OrderBy(id => id).ToList();
            var girderSpan = (double) SpinEditGirderSpan.Value;

            var ud = WindSpeed.GetUd(us10, z, groundType, riskArea);
            var ug = TrussGirderWind.GetUg(ud, girderSpan, groundType);

            IEnumerable<NodeLoad> nodeLoads;
            if (isTransverseLoad)
            {
                var beamCount = (int) SpinEditBeamCount.Value; //桁架片数
                var beamHeight = (double) SpinEditBeamHeight.Value;
                var beamDist = (double) SpinEditBeamHeight.Value;
                var areaRatio = (double) SpinEditAreaRatio.Value;
                Debug.Assert(nodeIds.Count % beamCount == 0);

                const double vehicleLoad = 1.5e3;
                var fg = TrussGirderWind.GetTransverseFg(ug, beamHeight, beamDist, beamCount, areaRatio) +
                         (_hasVehicle ? vehicleLoad : 0D);
                // 主梁桁架横桥向节点力(kN)
                var nodeForce = sign * fg * girderSpan * beamCount / nodeIds.Count * _forceUnit.Value();
                nodeLoads = nodeIds.Select(id => new NodeLoad {NodeId = id, Fy = nodeForce});
            }
            else
            {
                var perimeter = (double) SpinEditPerimeter.Value;
                var cf = (double) SpinEditCf.Value;

                var fg = TrussGirderWind.GetLongitudinalFg(ug, cf, perimeter);
                // 主梁桁架顺桥向节点力(kN)
                var nodeForce = sign * fg * girderSpan / nodeIds.Count * _forceUnit.Value();
                nodeLoads = nodeIds.Select(id => new NodeLoad {NodeId = id, Fx = nodeForce});
            }

            var loadCase = loadCaseDict[loadCaseName];
            loadCase.NodeLoads.Clear();
            loadCase.NodeLoads.AddRange(nodeLoads);

            //
            var outputLines = new List<string>
            {
                ";",
                $"; 工况: {loadCaseName}",
                $"; 结构组: {groupName}",
                $"; {(_hasVehicle ? "有车" : "无车")}",
                ";",
            };
            outputLines.AddRange(loadCase.ToUseStaticLoadKeywordBlocks());
            TextBoxGirderLoad.Text = string.Join(Environment.NewLine, outputLines);
        }

        private void ButtonCopyGirderLoadMct_OnClick(object sender, RoutedEventArgs e)
        {
            var text = TextBoxGirderLoad.Text;
            if (string.IsNullOrWhiteSpace(text))
                return;

            Clipboard.SetData(DataFormats.Text, text);
        }

        private void ButtonSaveGirderLoadMct_OnClick(object sender, RoutedEventArgs e)
        {
            var text = TextBoxGirderLoad.Text;
            if (string.IsNullOrWhiteSpace(text))
                return;

            var loadCaseName = (string) ComboBoxGirderLoadCase.SelectedItem;
            var groupName = (string) ComboBoxGirderElemGroup.SelectedItem;
            var sfd = new SaveFileDialog
            {
                FileName = $"荷载_{loadCaseName}_{groupName}_{(_hasVehicle ? "有车" : "无车")}",
                DefaultExt = "mct"
            };
            if (sfd.ShowDialog(this) != true)
                return;

            var filePath = sfd.FileName;
            File.WriteAllText(filePath, text, Encoding.GetEncoding("gb2312"));
        }

        #endregion

        #region 主塔

        private void ButtonGenerateTowerLoadMct_OnClick(object sender, RoutedEventArgs e)
        {
            if (TowerSizeItems.Count <= 0)
            {
                MessageBox.Show("请先加载索塔尺寸表！", "", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            var nodeDict = _mctModel.NodeDict;
            var elemDict = _mctModel.ElemDict;
            var groupDict = _mctModel.GroupDict;
            var loadCaseDict = _mctModel.StaticLoadCaseDict;

            //
            // 设计参数
            //
            var loadCaseName = (string) ComboBoxTowerLoadCase.SelectedItem;
            Debug.Assert(loadCaseName.Contains("横风") || loadCaseName.Contains("纵风"));
            var isTransverseLoad = loadCaseName.Contains("横风");
            _hasVehicle = loadCaseName.Contains("有车");
            var sign = loadCaseName.EndsWith("-") ? -1D : 1D;

            //
            var groundType = (GroundType) ComboBoxGroundType.SelectedItem;
            var riskArea = (RiskArea) ComboBoxRiskArea.SelectedItem;
            var groundZ = (double) SpinEditGroundZ.Value; //水面标高
            var deckZ = (double) SpinEditDeckZ.Value; //桥面标高
            Debug.Assert(deckZ > groundZ);

            var z = deckZ - groundZ; // 主梁基准高度Z
            var us10 = _hasVehicle
                ? WindSpeed.GetUs10FromUd((double) SpinEditUdw1.Value, z, groundType, riskArea) //桥上有车时，以桥面风速(最大25m/s)反算
                : (double) SpinEditUs10.Value; //桥上无车时，设计基本风速

            var groupName = (string) ComboBoxTowerElemGroup.SelectedItem;
            var elemIds = groupDict[groupName].ElemIds;

            var ys = TowerSizeItems.Select(item => item.Y).ToList();
            var ts = TowerSizeItems.Select(item => item.T).ToList();
            var ws = TowerSizeItems.Select(item => item.W).ToList();

            var minZ = elemIds.Min(elemId => elemDict[elemId].NodeIds.Min(nodeId => nodeDict[nodeId].Z));
            var maxZ = elemIds.Max(elemId => elemDict[elemId].NodeIds.Max(nodeId => nodeDict[nodeId].Z));
            var towerHeight = maxZ - minZ;
            Debug.Assert(towerHeight > 0);

            var nodeLoadDict = elemIds.SelectMany(elemId =>
            {
                var elem = elemDict[elemId];
                var node1 = nodeDict[elem.NodeIds[0]];
                var node2 = nodeDict[elem.NodeIds[1]];
                return new[] {node1.Id, node2.Id};
            }).Distinct().OrderBy(id => id).ToDictionary(id => id, id => new NodeLoad {NodeId = id});

            foreach (var elemId in elemIds)
            {
                var elem = elemDict[elemId];
                var node1 = nodeDict[elem.NodeIds[0]];
                var node2 = nodeDict[elem.NodeIds[1]];
                var elemLen = node1.DistTo(node2);
                var zc = (node1.Z + node2.Z) / 2; //单元中点z坐标
                z = zc - groundZ; // 基准高度Z
                Debug.Assert(z > 0);

                var ud = WindSpeed.GetUd(us10, z, groundType, riskArea);
                var ug = TowerWind.GetUg(ud, towerHeight, groundType);

                var index = ys.GetIndex(zc);
                var t = ts.Interpolate(index);
                var w = ws.Interpolate(index);

                if (isTransverseLoad)
                {
                    var fg = TowerWind.GetTransverseFg(ug, towerHeight, t, w);
                    var nodeForce = sign * fg * elemLen / 2 * _forceUnit.Value();
                    Debug.Assert(nodeLoadDict.ContainsKey(node1.Id) &&
                                 nodeLoadDict.ContainsKey(node2.Id));
                    nodeLoadDict[node1.Id].Fy += nodeForce;
                    nodeLoadDict[node2.Id].Fy += nodeForce;
                }
                else
                {
                    var fg = TowerWind.GetLongitudinalFg(ug, towerHeight, t, w);
                    var nodeForce = sign * fg * elemLen / 2 * _forceUnit.Value();
                    Debug.Assert(nodeLoadDict.ContainsKey(node1.Id) &&
                                 nodeLoadDict.ContainsKey(node2.Id));
                    nodeLoadDict[node1.Id].Fx += nodeForce;
                    nodeLoadDict[node2.Id].Fx += nodeForce;
                }
            }

            var loadCase = loadCaseDict[loadCaseName];
            loadCase.NodeLoads.Clear();
            loadCase.NodeLoads.AddRange(nodeLoadDict.Select(nl => nl.Value));

            //
            var outputLines = new List<string>
            {
                ";",
                $"; 工况: {loadCaseName}",
                $"; 结构组: {groupName}",
                $"; {(_hasVehicle ? "有车" : "无车")}",
                ";",
            };
            outputLines.AddRange(loadCase.ToUseStaticLoadKeywordBlocks());
            TextBoxTowerLoad.Text = string.Join(Environment.NewLine, outputLines);
        }

        private void ButtonCopyTowerLoadMct_OnClick(object sender, RoutedEventArgs e)
        {
            var text = TextBoxTowerLoad.Text;
            if (string.IsNullOrWhiteSpace(text))
                return;

            Clipboard.SetData(DataFormats.Text, text);
        }

        private void ButtonSaveTowerLoadMct_OnClick(object sender, RoutedEventArgs e)
        {
            var text = TextBoxTowerLoad.Text;
            if (string.IsNullOrWhiteSpace(text))
                return;

            var loadCaseName = (string) ComboBoxTowerLoadCase.SelectedItem;
            var groupName = (string) ComboBoxTowerElemGroup.SelectedItem;
            var sfd = new SaveFileDialog {FileName = $"荷载_{loadCaseName}_{groupName}", DefaultExt = "mct"};
            if (sfd.ShowDialog(this) != true)
                return;

            var filePath = sfd.FileName;
            File.WriteAllText(filePath, text, Encoding.GetEncoding("gb2312"));
        }

        private void GridTowerSizeData_OnPastingFromClipboard(object sender, PastingFromClipboardEventArgs e)
        {
            TowerSizeItems.Clear();
        }

        public class TowerSizeItem
        {
            [Index(0)] public double Y { get; set; }
            [Index(1)] public double T { get; set; }
            [Index(2)] public double W { get; set; }
        }

        private void MenuItemTowerSizeData_OnClick(object sender, RoutedEventArgs e)
        {
            var ofd = new OpenFileDialog {DefaultExt = ".txt", Filter = "Txt file (.txt)|*.txt"};
            if (ofd.ShowDialog(this) != true)
                return;

            TowerSizeItems.Clear();
            using (var reader = new StreamReader(ofd.FileName))
            using (var csv = new CsvReader(reader))
            {
                csv.Configuration.HasHeaderRecord = false;
                csv.Configuration.Delimiter = "\t";
                csv.GetRecords<TowerSizeItem>()
                    .ForEach(item => TowerSizeItems.Add(item));
            }
        }

        #endregion

        #region 斜拉索

        private void ButtonGenerateCableLoadMct_OnClick(object sender, RoutedEventArgs e)
        {
            var nodeDict = _mctModel.NodeDict;
            var elemDict = _mctModel.ElemDict;
            var groupDict = _mctModel.GroupDict;
            var loadCaseDict = _mctModel.StaticLoadCaseDict;

            //
            // 设计参数
            //
            var groundType = (GroundType) ComboBoxGroundType.SelectedItem;
            var riskArea = (RiskArea) ComboBoxRiskArea.SelectedItem;
            var groundZ = (double) SpinEditGroundZ.Value; //水面标高
            var deckZ = (double) SpinEditDeckZ.Value; //桥面标高
            Debug.Assert(deckZ > groundZ);

            var z = deckZ - groundZ; // 主梁基准高度Z
            var us10 = _hasVehicle
                ? WindSpeed.GetUs10FromUd((double) SpinEditUdw1.Value, z, groundType, riskArea) //桥上有车时，以桥面风速(最大25m/s)反算
                : (double) SpinEditUs10.Value; //桥上无车时，设计基本风速

            //
            var loadCaseName = (string) ComboBoxCableLoadCase.SelectedItem;
            Debug.Assert(loadCaseName.Contains("横风") || loadCaseName.Contains("纵风"));
            var isTransverseLoad = loadCaseName.Contains("横风");
            _hasVehicle = loadCaseName.Contains("有车");
            var sign = loadCaseName.EndsWith("-") ? -1D : 1D;

            var groupName = (string) ComboBoxCableElemGroup.SelectedItem;
            var elemIds = groupDict[groupName].ElemIds;

            var cd = (double) SpinEditCableCd.Value; //斜拉索的阻尼系数Cd
            var dc = (double) SpinEditCableDiameter.Value; //斜拉索直径

            var nodeLoadDict = elemIds.SelectMany(elemId =>
            {
                var elem = elemDict[elemId];
                var node1 = nodeDict[elem.NodeIds[0]];
                var node2 = nodeDict[elem.NodeIds[1]];
                return new[] {node1.Id, node2.Id};
            }).Distinct().OrderBy(id => id).ToDictionary(id => id, id => new NodeLoad {NodeId = id});

            foreach (var elemId in elemIds)
            {
                var elem = elemDict[elemId];
                var node1 = nodeDict[elem.NodeIds[0]];
                var node2 = nodeDict[elem.NodeIds[1]];
                var elemLen = node1.DistTo(node2);
                var zc = (node1.Z + node2.Z) / 2; //单元中点z坐标
                z = zc - groundZ; // 基准高度Z
                Debug.Assert(z > 0);

                Debug.Assert(Abs(node2.X - node1.X) > 0);
                var alphac = Atan(Abs(node2.Z - node1.Z) / Abs(node2.X - node1.X));

                var horizontalLength = Abs(node1.X - node2.X);
                var ud = WindSpeed.GetUd(us10, z, groundType, riskArea);
                var ug = CableWind.GetUg(ud, horizontalLength, groundType);

                if (isTransverseLoad)
                {
                    var fg = CableWind.GetTransverseFg(ug, cd, dc);
                    var nodeForce = sign * fg * elemLen / 2 * _forceUnit.Value();
                    Debug.Assert(nodeLoadDict.ContainsKey(node1.Id) &&
                                 nodeLoadDict.ContainsKey(node2.Id));
                    nodeLoadDict[node1.Id].Fy += nodeForce;
                    nodeLoadDict[node2.Id].Fy += nodeForce;
                }
                else
                {
                    var fg = CableWind.GetLongitudinalFg(ug, cd, dc, alphac);
                    var nodeForce = sign * fg * elemLen / 2 * _forceUnit.Value();
                    Debug.Assert(nodeLoadDict.ContainsKey(node1.Id) &&
                                 nodeLoadDict.ContainsKey(node2.Id));
                    nodeLoadDict[node1.Id].Fx += nodeForce;
                    nodeLoadDict[node2.Id].Fx += nodeForce;
                }
            }

            var loadCase = loadCaseDict[loadCaseName];
            loadCase.NodeLoads.Clear();
            loadCase.NodeLoads.AddRange(nodeLoadDict.Select(nl => nl.Value));

            //
            var outputLines = new List<string>
            {
                ";",
                $"; 工况: {loadCaseName}",
                $"; 结构组: {groupName}",
                $"; {(_hasVehicle ? "有车" : "无车")}",
                ";",
            };
            outputLines.AddRange(loadCase.ToUseStaticLoadKeywordBlocks());
            TextBoxCableLoad.Text = string.Join(Environment.NewLine, outputLines);
        }

        private void ButtonCopyCableLoadMct_OnClick(object sender, RoutedEventArgs e)
        {
            var text = TextBoxCableLoad.Text;
            if (string.IsNullOrWhiteSpace(text))
                return;

            Clipboard.SetData(DataFormats.Text, text);
        }

        private void ButtonSaveCableLoadMct_OnClick(object sender, RoutedEventArgs e)
        {
            var text = TextBoxCableLoad.Text;
            if (string.IsNullOrWhiteSpace(text))
                return;

            var loadCaseName = (string) ComboBoxCableLoadCase.SelectedItem;
            var groupName = (string) ComboBoxCableElemGroup.SelectedItem;
            var sfd = new SaveFileDialog {FileName = $"荷载_{loadCaseName}_{groupName}", DefaultExt = "mct"};
            if (sfd.ShowDialog(this) != true)
                return;

            var filePath = sfd.FileName;
            File.WriteAllText(filePath, text, Encoding.GetEncoding("gb2312"));
        }

        #endregion
    }
}